package logger

import (
	"fmt"
	"os"
	"path"
	"runtime"
	"strings"
	"sync"
	"time"
)

type caller struct {
	file string
	name string
	line int
}
type log struct {
	level     uint8
	dest      string
	destFile  *os.File
	fileSize  int64
	logNumber int32
	realDest  string
	*caller
	data chan *data
	wg   *sync.WaitGroup
}

var once sync.Once
var levelMap = map[string]uint8{
	"trace":   0,
	"debug":   1,
	"warning": 2,
	"info":    3,
	"error":   4,
	"fatal":   5,
	"Trace":   0,
	"Debug":   1,
	"Warning": 2,
	"Info":    3,
	"Error":   4,
	"Fatal":   5,
}

type data struct {
	logType string
	*caller
	value interface{}
}

func NewLogger(level string, dest string, fileSize int64) *log {
	data := make(chan *data, 10)
	return &log{
		level:    levelMap[level],
		dest:     dest,
		fileSize: fileSize,
		realDest: dest,
		wg:       &sync.WaitGroup{},
		data:     data,
	}
}
func (l *log) getInfo(n int) (funcName, file string, line int) {
	pc, file, line, ok := runtime.Caller(n)
	if !ok {
		fmt.Println("missing runtime.Caller")
		return
	}
	funcName = runtime.FuncForPC(pc).Name()
	cl := &caller{
		file: file,
		line: line,
		name: funcName,
	}
	l.caller = cl
	//file = path.Base(file)
	return
}
func (l *log) checkSize(file *os.File) bool {
	if file == nil {
		return false
	}
	fileInfo, err := file.Stat()
	if err != nil {
		fmt.Printf("cat not check the size of log file:%s\n", err)
		return false
	}
	fmt.Println(fileInfo.Size())
	return fileInfo.Size() >= l.fileSize
}
func (l *log) output(logType string, caller *caller, v interface{}) {
	now := time.Now()
	defer l.wg.Done()
	if l.dest == "console" {
		if l.level >= levelMap["Error"] {
			_, err := fmt.Fprintf(os.Stderr, "%s\t[file: %s  func: %s  line:%d]\t[%s]\t%v\n", now.Format("2006-01-02 15:04:05"), caller.file, caller.name, caller.line, logType, v)
			if err != nil {
				return
			}
		} else {
			_, err := fmt.Fprintf(os.Stdout, "%s\t[file: %s  func: %s  line:%d]\t[%s]\t%v\n", now.Format("2006-01-02 15:04:05"), caller.file, caller.name, caller.line, logType, v)
			if err != nil {
				return
			}
		}
	} else {
		var err error
		if l.checkSize(l.destFile) {
			l.destFile.Close()
			l.logNumber += 1
			suffix := path.Ext(l.dest)
			fileNameWithoutSuffix := strings.TrimSuffix(l.dest, suffix)
			l.realDest = fmt.Sprintf("%s-%d%s", fileNameWithoutSuffix, l.logNumber, suffix)
			fmt.Println(l.realDest)
		}
		l.destFile, err = os.OpenFile(l.realDest, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
		if err != nil {
			fmt.Printf("open log file failed:%s", err)
			return
		}
		_, err2 := fmt.Fprintf(l.destFile, "%s\t[file: %s  func: %s  line:%d]\t[%s]\t%v\n", now.Format("2006-01-02 15:04:05"), caller.file, caller.name, caller.line, logType, v)
		if err2 != nil {
			return
		}
		//defer destFile.Close()
	}
}
func (l *log) worker(workID int, data <-chan *data) {
	for d := range data {
		//var buf = make([]byte, 64)
		//var stk = buf[:runtime.Stack(buf, false)]
		//print(string(stk))
		l.output(d.logType, d.caller, d.value)
	}
}
func (l *log) handler(logType string, v interface{}) {
	l.wg.Add(1)
	l.getInfo(3)
	l.data <- &data{
		logType: logType,
		caller:  l.caller,
		value:   v,
	}
	once.Do(func() {
		go l.worker(1, l.data)
		go l.worker(2, l.data)
		go l.worker(3, l.data)
	})
	l.wg.Wait()
}
func (l *log) Trace(v interface{}) {
	if l.level <= 0 {
		l.handler("Trace", v)
	}
}
func (l *log) Debug(v interface{}) {
	if l.level <= 1 {
		l.handler("Debug", v)
	}
}
func (l *log) Warning(v interface{}) {
	if l.level <= 2 {
		l.handler("Warning", v)
	}
}
func (l *log) Info(v interface{}) {
	if l.level <= 3 {
		l.handler("Info", v)
	}
}
func (l *log) Error(v interface{}) {
	if l.level <= 4 {
		l.handler("Error", v)
	}
}
func (l *log) Fatal(v interface{}) {
	if l.level <= 5 {
		l.handler("Fatal", v)
		os.Exit(1)
	}
}
